package com.lixueju.security.box.core.redis;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *      redis缓存相关操作工具
 * </p>
 *
 * @author lixueju
 * @since 2019-03-19
 */
@Component
public class SecurityBoxRedisCache {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    /**
     * 根据key获取字符串类型的值
     *
     * @param key 数据的key值
     * @return 储存的内容
     */
    public String getString(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 存值字符串类型
     *
     * @param key     储存的key值
     * @param value   需要储存的内容
     * @param seconds 有效时常，单位秒
     * @return boolean 是否储存成功，true成功，false失败
     */
    public boolean setString(String key, String value, long seconds) {
        return setString(key, value, seconds, TimeUnit.SECONDS);
    }

    /**
     * 存值字符串类型
     *
     * @param key      储存的key值
     * @param value    需要储存的内容
     * @param time     存储有效期
     * @param timeUnit 时间单元：秒（TimeUnit.SECONDS）、分（TimeUnit.MINUTES）、时（TimeUnit.HOURS）、天（（TimeUnit.DAYS））
     * @return
     */
    public boolean setString(String key, String value, long time, TimeUnit timeUnit) {
        if (timeUnit == null) {
            stringRedisTemplate.boundValueOps(key).set(value, time, TimeUnit.SECONDS);
        } else {
            stringRedisTemplate.boundValueOps(key).set(value, time, timeUnit);
        }
        Long expire = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
        return expire > 0;
    }


    /**
     * 删除数据
     *
     * @param key 需要删除的内容key
     * @return 只要没有出错只返回true
     */
    public boolean del(String key) {
        return stringRedisTemplate.delete(key);
    }


    /**
     * 获取指定对象
     *
     * @param key   需要获取的key
     * @param clazz 指定的clazz
     * @return 返回传入的类型对象
     */
    public <T> T getObject(String key, Class<T> clazz) {
        try {
            String string = getString(key);
            if(string==null){
                return null;
            }
            //转成对象
            return JSON.parseObject(string, clazz);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取指定List对象
     *
     * @param key   需要获取的key
     * @param clazz 指定的clazz
     * @return 返回传入的类型对象
     */
    public <T> List<T> getListObject(String key, Class<T> clazz) {
        try {
            String string = getString(key);
            if(string==null){
                return null;
            }
            //转成List对象
            return JSONObject.parseArray(string,clazz);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 储存对象
     *
     * @param key     储存的key值
     * @param obj     需要储存的对象
     * @param seconds 有效时常，单位秒
     * @return boolean 是否储存成功，true成功，false失败
     */
    public <T> boolean setObject(String key, T obj, long seconds) {
        return setObject(key, obj, seconds, TimeUnit.SECONDS);
    }

    /**
     * 存值字符串类型
     *
     * @param key      储存的key值
     * @param obj      需要储存的对象
     * @param time     存储有效期
     * @param timeUnit 时间单元：秒（TimeUnit.SECONDS）、分（TimeUnit.MINUTES）、时（TimeUnit.HOURS）、天（（TimeUnit.DAYS））
     * @return
     */
    public <T> boolean setObject(String key, T obj, long time, TimeUnit timeUnit) {
        if (timeUnit == null) {
            setString(key, JSON.toJSONString(obj), time);
        } else {
            setString(key, JSON.toJSONString(obj), time, timeUnit);
        }
        Long expire = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
        return expire > 0;
    }

    /**
     * 获取过期时间
     * @param key 需要查询的key
     * @return 距离过期剩余的时间（秒）
     */
    public Long  getExpire(String key){
       return stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /*------ redis分布式锁相关代码 begin ------*/
    private static final long DEFAULT_WAIT_LOCK_TIME_OUT = 60;//等待获取锁的时间，60s 有慢sql，超时时间设置长一点
    private static final long DEFAULT_EXPIRE = 80;   //锁的超时时间80s 有慢sql，超时时间设置长一点

    /**
     * 获得redis分布式锁
     * @param lock 锁对象
     * @return
     */
    public boolean lock(RedisLock lock) throws Exception{
        return lock(lock.getLockName(), lock.getWaitSeconds(), lock.getExpireTime());
    }

    /**
     * 根据key获得锁
     * @param lockKey 锁的名字
     * @return
     */
    public boolean lock(String lockKey) throws Exception{
        return lock(lockKey, DEFAULT_WAIT_LOCK_TIME_OUT, DEFAULT_EXPIRE);
    }

    /**
     * 根据key获得锁
     * @param lockKey 锁的名字
     * @param waitSeconds 拿锁的等待时间，超时则拿锁失败
     * @return
     */
    public boolean lock(String lockKey, Long waitSeconds) throws Exception{
        return lock(lockKey, waitSeconds, DEFAULT_EXPIRE);
    }

    /**
     * 根据key获得锁
     * @param lockKey 锁的名字
     * @param waitSeconds 拿锁的等待时间，超时则拿锁失败
     * @param expireTime 锁的过期时间
     * @return
     */
    public boolean lock(String lockKey, Long waitSeconds, Long expireTime) throws Exception {

        Long timeout = waitSeconds * 1000;
        boolean operResult = false;
        RedisConnection connection = null;

        long start = System.currentTimeMillis();
        long now;
        String startTimestamp = String.valueOf(start);
        try {
            now = System.currentTimeMillis();
            while ((now - start) < timeout) {
                connection = stringRedisTemplate.getConnectionFactory().getConnection();
                if (connection.setNX(lockKey.getBytes(), startTimestamp.getBytes())) {
                    //redisTemplate.expire(lockKey, DEFAULT_EXPIRE, TimeUnit.SECONDS);//暂设置为80s过期，防止异常中断锁未释放
                    stringRedisTemplate.expire(lockKey, expireTime, TimeUnit.SECONDS);// 默认设置为80s过期，防止异常中断锁未释放

                    operResult = true;
                    break;
                }

                TimeUnit.SECONDS.sleep(1);
                now = System.currentTimeMillis();
            }

            if((now - start) > timeout) {
                operResult = false;
            }
        } catch (Exception e) {
            // 加锁出错，释放锁，抛出异常
            unlock(lockKey);

            return false;
        } finally {
            if(null != connection) {
                connection.close();
            }
        }

        return operResult;
    }

    /**
     * 释放锁
     * @param lock 锁对象
     * @return
     */
    public void unlock(RedisLock lock) {
        unlock(lock.getLockName());
    }

    /**
     * 根据key释放锁
     * @param lockKey 锁的名字
     * @return
     */
    public void unlock(String lockKey) {

        RedisConnection connection = stringRedisTemplate.getConnectionFactory().getConnection();
        connection.del(lockKey.getBytes());
        connection.close();
    }
    /*------ redis分布式锁相关代码 end ------*/

}
